Android 日志框架Logger使用教程及源码分析

您所在的位置:网站首页 logger android Android 日志框架Logger使用教程及源码分析

Android 日志框架Logger使用教程及源码分析

#Android 日志框架Logger使用教程及源码分析| 来源: 网络整理| 查看: 265

Logger 使用 1. Gradle 依赖: implementation 'com.orhanobut:logger:2.2.0' 复制代码 2. 添加 Adapter Logger.addLogAdapter(new AndroidLogAdapter()); 复制代码

传进来的适配器被添加到Printer中去了,Printer只是一个接口,它的实现类是LoggerPrinter。所以再来看看LoggerPrinter的addAdapter方法。

private final List logAdapters = new ArrayList(); ... @Override public void addAdapter(@NonNull LogAdapter adapter) { logAdapters.add(checkNotNull(adapter)); } 复制代码

可以看到我们一开始传进来的log适配器最终被放进了一个列表里,这个列表存放了各种策略的log适配器,后面会解释它的的作用。至此,Logger初始化就结束了。

接下来就是可以直接使用了

Logger.d("message"); 复制代码

默认输出 Log 以及所在类名,方法名,行号,线程等相关信息。如果不想显示除 Log 外的其他信息,可以对 Adapter 进行相关设置,

FormatStrategy formatStrategy = PrettyFormatStrategy.newBuilder() .showThreadInfo(false) // 展示线程信息 .methodCount(5) // 展示调用的方法个数,默认是 2 .methodOffset(0) // 跳过堆栈中的方法个数, 默认是 0 .tag("My custom tag") // TAG 内容. 默认是 PRETTY_LOGGER .build(); Logger.addLogAdapter(new AndroidLogAdapter(formatStrategy)); 复制代码

显示如下,如果 methodOffset 为1,则不展示 MainActivity.onCreate(),只显示其他 4 个。

如果需要将日志存储到本地文件,则需要使用 DiskLogAdapter, 同时需要声明存储权限,Logger 默认生成 csv 文件,存储在 /storage/emulated/0/logger 目录下,

Logger.addLogAdapter(new DiskLogAdapter()); 复制代码

如果针对不同的页面 Logger 的配置不同, 可以使用 Logger.clearLogAdapters(), 然后进行重新配置。

3. 其他类型

message 除了是 String 类型,还可以是集合,数组,

Logger.t("tag").e("Custom tag for only one use"); Logger.json("{ "key": 3, "value": something}"); Logger.d(Arrays.asList("foo", "bar")); Logger.d(new int[]{2,3,19,4}); Logger.d(""); Map map = new HashMap(); map.put("key", "value"); map.put("key1", "value2"); Logger.d(map); Logger.d("message %s", " erro"); 复制代码

这五个方法的实现其实区别不大,这里就以Logger.d方法为例来分析,跳进去看看。

//Logger.java public static void d(@Nullable Object object) { printer.d(object); } //LoggerPrinter.java @Override public void d(@NonNull String message, @Nullable Object... args) { log(DEBUG, null, message, args); } @Override public void d(@Nullable Object object) { log(DEBUG, null, Utils.toString(object)); } 复制代码

前面说过Printer的实现类是LoggerPrinter,所以实际上是调用了LoggerPrinter方法。再深入log方法看看。

/** * This method is synchronized in order to avoid messy of logs' order. */ private synchronized void log(int priority, @Nullable Throwable throwable, @NonNull String msg, @Nullable Object... args) { checkNotNull(msg); //1 String tag = getTag(); //2 String message = createMessage(msg, args); //3 log(priority, tag, message, throwable); } 复制代码

注释上说使用synchronized是为了防止日志打印顺序错乱。这是合理的,毕竟可能会有多个线程同时调用这个方法。这里有三个步骤,先获取tag,看看getTag方法。

private final ThreadLocal localTag = new ThreadLocal(); ... @Nullable private String getTag() { String tag = localTag.get(); if (tag != null) { localTag.remove(); return tag; } return null; } 复制代码

这里使用ThreadLocal了来区分不同的线程,也就是说不同的线程得到的Tag可能不一样,后面会解释原因。接着再来看createMessage方法。

@NonNull private String createMessage(@NonNull String message, @Nullable Object... args) { return args == null || args.length == 0 ? message : String.format(message, args); } 复制代码

可以看到当初调用Logger.d()的时候如果传入的只有字符串没有后面的参数,则直接返回这个字符串,如果传了参数就把这个字符串格式化。比如Logger.d(“num:%d”,123),这时返回的就是 “num:123” 这个字符串。最后再来看看log方法做了什么。

@Override public synchronized void log(int priority, @Nullable String tag, @Nullable String message, @Nullable Throwable throwable) { if (throwable != null && message != null) { message += " : " + Utils.getStackTraceString(throwable); } if (throwable != null && message == null) { message = Utils.getStackTraceString(throwable); } if (Utils.isEmpty(message)) { message = "Empty/NULL log message"; } //1 for (LogAdapter adapter : logAdapters) { if (adapter.isLoggable(priority, tag)) { adapter.log(priority, tag, message); } } } 复制代码

看到代码1的for循环,上面说过,在初始化的时候我们会通过Logger.addLogAdapterf方法把log适配器添加到logAdapters这个列表中,现在将所有的适配器都遍历一遍,调用它们log方法。比如,我们添加了一个logcat适配器和文件适配器,那么调用 Logger.d(“hello world”) 后就会在logcat和文件中打印这个日志了。

Loggger还能打印json和xml,基本使用如下。

//{"name":"PYJTLK","isHandsome":false} Logger.json("{"name":"PYJTLK","isHandsome":false}"); //PYJTLKfalse Logger.xml("PYJTLKfalse"); 复制代码

实现非常简单,来看看代码。

@Override public void json(@Nullable String json) { if (Utils.isEmpty(json)) { d("Empty/Null json content"); return; } try { json = json.trim(); //单个json if (json.startsWith("{")) { JSONObject jsonObject = new JSONObject(json); String message = jsonObject.toString(JSON_INDENT); d(message); return; } //json数组 if (json.startsWith("[")) { JSONArray jsonArray = new JSONArray(json); String message = jsonArray.toString(JSON_INDENT); d(message); return; } e("Invalid Json"); } catch (JSONException e) { e("Invalid Json"); } } 复制代码

再看看xml的解析。

@Override public void xml(@Nullable String xml) { if (Utils.isEmpty(xml)) { d("Empty/Null xml content"); return; } try { Source xmlInput = new StreamSource(new StringReader(xml)); StreamResult xmlOutput = new StreamResult(new StringWriter()); Transformer transformer = TransformerFactory.newInstance().newTransformer(); transformer.setOutputProperty(OutputKeys.INDENT, "yes"); transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2"); transformer.transform(xmlInput, xmlOutput); d(xmlOutput.getWriter().toString().replaceFirst(">", ">\n")); } catch (TransformerException e) { e("Invalid xml"); } } 复制代码 临时TAG

文章开头初始化Logger后,调用Logger.d()方法使用TAG是全局的。如果要临时使用其他的TAG,调用Logger.t().d()就可以了。

Logger.t("MyTag2").d("hello world"); 复制代码 /** * Given tag will be used as tag only once for this method call regardless of the tag that's been * set during initialization. After this invocation, the general tag that's been set will * be used for the subsequent log calls */ public static Printer t(@Nullable String tag) { return printer.t(tag); } 复制代码

注释上的意思是说调用这个方法传进来的TAG只会使用一次,这个方法结束后,Logger又会用回之前的全局TAG。如此一来就能做到临时变更TAG了,继续深入这个方法看看。

@Override public Printer t(String tag) { if (tag != null) { localTag.set(tag); } return this; } 复制代码

前面说过localTag是一个ThreadLocal,这里将临时TAG放了进去,接着再回顾一下上面使用到localTag的地方。

@Nullable private String getTag() { String tag = localTag.get(); if (tag != null) { localTag.remove(); return tag; } return null; } 复制代码

在打印的时候Logger就会把它取出来,然后localTag的TAG被移除,如此一来就实现了临时TAG的功能。这里需要注意,t() 和 d()是连着用的。如果先在线程A上调用t(),再到线程B调用d(),是无效的。那是因为两个线程的ThreadLocal的TAG值是不一样的

//有效,因为在同一个线程同时调用t()和d() Logger.t("MyTag2").d("hello world"); //无效,此时线程A的ThreadLocal存着临时TAG,可线程B的ThreadLocal中存放空值 Logger.t("MyTag2");//线程A Logger.d("hello world");//线程B 复制代码 存储分析

上传客户端日志,对于分析 app 运行情况可用户使用习惯是至关重要的一步,但是 Logger 的存储路径试固定的,没有提供相关的 api 进行设置,所以可以通过分析其中的原理,然后自定义一个 adapter. 对于这里的路径我建议存储在 /Android/data/包名, 因为此路径不需要获取用户权限,可以直接使用。

通过 new DiskLogAdapter(), 在其构造方法中 CsvFormatStrategy 使用 Builder 模式 创建了一个 CsvFormatStrategy 实例, 在 builder() 可以看到具体的路径,因为一旦在子线程中操作,Handler 需要手动启动 Looper, 所以这里 通过 HandlerThread 获取 Looper 传递给 Handler, 通过 DiskLogStrategy.WriteHandler 创建 Handler, 将将文件路径传递给 DiskLogStrategy.WriteHandler, 同时通过构造方法生成 DiskLogStrategy 对象,将 handler 传递给 DiskLogStrategy, 当输出 Log 时调用 DiskLogStrategy 的 log() 方法,通过 log() 方法中 handler 将 message 发送出去, 然后交给 handler 处理,存储到 DiskLogStrategy.WriteHandler 中的路径。



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3